1. PREPARE

The Text Mining Learning Labs during the second week of the Summer Workshop are guided by a recent publication by Rosenberg et al. (2020) and will focus on analyzing tweets about the Next Generation Science Standards (NGSS) and Common Core State Standards (CCSS). We’ll dive deeper into these tweets in Text Mining (TM) Module 1: Public Sentiment and the State Standards. For now, this supplemental learning lab is designed to help you understand how data used in the study and the learning labs was collected. More importantly though, this supplemental lab is will get you up and running with the Twitter API in case you are interested in using data from Twitter in your own research or evaluation work.

1a. Load Libraries

tidyverse 📦

As noted in our Getting Started activity, R uses “packages” and add-ons that enhance its functionality. One package that we’ll be using extensively is {tidyverse}. The {tidyverse} package is actually a collection of R packages designed for reading, wrangling, and exploring data and which all share an underlying design philosophy, grammar, and data structures.

Click the green arrow in the right corner of the “code chunk” that follows to load the {tidyverse} library.

library(tidyverse)
Registered S3 methods overwritten by 'dbplyr':
  method         from
  print.tbl_lazy     
  print.tbl_sql      
── Attaching packages ────────────────────────────────────────────────────────────── tidyverse 1.3.1 ──
✓ tibble  3.1.2     ✓ stringr 1.4.0
✓ purrr   0.3.4     ✓ forcats 0.5.1
── Conflicts ───────────────────────────────────────────────────────────────── tidyverse_conflicts() ──
x dplyr::filter()  masks stats::filter()
x purrr::flatten() masks rtweet::flatten()
x dplyr::lag()     masks stats::lag()

Again, don’t worry if you saw a number of messages: those probably mean that the tidyverse loaded just fine. Any conflicts you may have seen mean that functions in these packages you loaded have the same name as functions in other packages and R will default to function from the last loaded package unless you specify otherwise.

rtweet 📦

The rtweet package provides users a range of functions designed to extract data from Twitter’s REST and streaming APIs and has three main goals:

  1. Formulate and send requests to Twitter’s REST and stream APIs.

  2. Retrieve and iterate over returned data.

  3. Wrangling data into tidy structures.

Let’s load the {rtweet} package that we’ll be using to accomplish all three of the goals listed above:

library(rtweet)

1b. Authorize RStudio

In order to authorize R to use your Twitter App to retrieve data, you’ll need to create a personal Twitter token by completing the following steps:

  1. Navigate to developer.twitter.com/en/apps and select your Twitter app
  2. Click the tab labeled Keys and tokens to retrieve your keys.
  3. Locate the Consumer API keys (aka “API Secret”).

  1. Scroll down to Access token & access token secret and click Create

  1. Copy and paste the following code into the R Script file named twitter-auth.R located in files pane, replace the four fake keys with your own, and pass them along to create_token() function.
## store api keys (these are fake example values; replace with your own keys)
app_name <- "Text Mining in Education"
api_key <- "afYS4vbIlPAj096E60c4W1fiK"
api_secret_key <- "bI91kqnqFoNCrZFbsjAWHD4gJ91LQAhdCJXCj3yscfuULtNkuu"
access_token <- "9551451262-wK2EmA942kxZYIwa5LMKZoQA4Xc2uyIiEwu2YXL"
access_token_secret <- "9vpiSGKg1fIPQtxc5d5ESiFlZQpfbknEN1f1m2xe5byw7"

## authenticate via web browser
token <- create_token(
  app = app_name,
  consumer_key = api_key,
  consumer_secret = api_secret_key,
  access_token = access_token,
  access_secret = access_token_secret)

Note: these keys are named secret for a reason. We store these up in a separate R script file rather than in a R Markdown file that you will eventually share.

Check Authorization

The create_token() function should automatically save your token as an environment variable. So next time you start an R session – on the same machine – rtweet should 🤞 automatically find your token.

  1. To make sure it works, you can run the following code and check to make sure the app name and api_key match those on your dev account.
## check to see if the token is loaded
get_token()
<Token>
<oauth_endpoint>
 request:   https://api.twitter.com/oauth/request_token
 authorize: https://api.twitter.com/oauth/authenticate
 access:    https://api.twitter.com/oauth/access_token
<oauth_app> Text Mining in Education
  key:    6R7OuvUoXm5XLuZS6Wk0WrtIn
  secret: <hidden>
<credentials> oauth_token, oauth_token_secret
---

That’s it! You’re ready ready to start wrangling some tweets!!!

2. WRANGLE

2a. Search Tweets

Constructing a Query

Since one of our goals for TM Module 1 is a simplistic replication of the study by (Rosenberg et al. 2020), let’s begin by introducing the search_tweets() function to mine some tweets about the Next Generation Science Standards.

Use the code chunk below to run the following code to request from Twitter’s API 5,000 tweets containing the NGSSchat hashtag and store as a new data frame called ngss_tweets_q1:

ngss_tweets_q1 <- search_tweets(q = "#NGSSchat", 
                                n = 5000)

Note that the first argument the search_tweets() function expects, q =, is the search term included in quotation marks and that n = specifies the maximum number of tweets we want.

👉 Your Turn ⤵

Use the code chunk below to view your new ngss_tweets_q1 data frame using one of the methods previously introduced to help answer the following questions:

# your code here
  1. How many tweets did our first query using the Twitter API actually return? How many variables?

  2. Why do you think our query pulled in far less than 5,000 tweets requested? Hint.

  3. How many tweets are returned if you don’t include the n = argument?

  4. Does our query also include retweets? How do you know?

  5. Does capitalization in your query matter? Use the code chunk below to find out.

# your code here 

Using the OR Operator

In Understanding public sentiment about educational reforms: The Next Generation Science Standards on Twitter, Rosenberg et al. (2020) accessed tweets and user information from the hashtag-based #NGSSchat online community, including all tweets that used the following phrases: “ngss,” “next generation science standard/s,” “next gen science standard/s.” Note that “/” indicates an additional phrase featuring the respective plural form.

Let’s modify our query using the OR operator to also include “ngss” so it will return tweets containing either #NGSSchat or “ngss” and assign to ngss_or_tweets:

ngss_tweets_q2 <- search_tweets(q = "#NGSSchat OR ngss", 
                                n = 5000)

Downloading [=>---------------------------------------]   4%
Downloading [=>---------------------------------------]   6%
Downloading [==>--------------------------------------]   8%
Downloading [===>-------------------------------------]  10%
Downloading [====>------------------------------------]  12%
Downloading [=====>-----------------------------------]  14%
Downloading [======>----------------------------------]  16%
Downloading [======>----------------------------------]  18%
Downloading [=======>---------------------------------]  20%
Downloading [========>--------------------------------]  22%
Downloading [=========>-------------------------------]  24%
Downloading [==========>------------------------------]  26%
ngss_tweets_q2

👉 Your Turn ⤵

In the following code chunk, try including both search terms but excluding the OR operator to answer the questions below:

# your code here
  1. Does excluding the OR operator return more tweets, the same number of tweets, or fewer tweets? Why?

  2. Does our query also include tweets containing the #ngss hashtag?

  3. What other useful arguments does the search_tweet() function contain? Try adding one and see what happens.

Hint: Use the ?search_tweets help function to learn more about the q argument and other arguments for composing search queries.

Using Multiple Queries

Unfortunately, the OR operator will only get us so far. In order to include the additional search terms, we will need to use the c() function to combine our search terms into a single list.

The rtweets package has an additional search_tweets2() function for using multiple queries in a search. To do this, either wrap single quotes around a search query using double quotes, e.g., q = '"next gen science standard"' or escape each internal double quote with a single backslash, e.g., q = "\"next gen science standard\"".

Copy and past the following code to store the results of our query in ngss_tweets:

ngss_tweets_q3 <- search_tweets2(q = c("#NGSSchat OR ngss",
                                       '"next generation science standard"'),
                                 n = 5000)

Notice the unique syntax required for the query argument. For example, when “OR” is entered between search terms, query = "#NGSSchat OR ngss", Twitter’s REST API should return any tweet that contains either “#NGSSchat” or “ngss.” It is also possible to search for exact phrases using double quotes. To do this, either wrap single quotes around a search query using double quotes, e.g., q = '"next generation science standard"' as we did above, or escape each internal double quote with a single backslash, e.g., q = "\"next generation science standard\"".

Our First Dictionary

We still have a few queries to add in order to replicate the approach by Rosenberg et al, but dealing with that many queries inside a single function is a bit tedious.

Let’s go ahead and create our very first “dictionary” — we’ll learn more about dictionary-based approaches to text mining in Learning Lab 3 — for identifying tweets related to the NGSS standards, and then pass that dictionary to the q = query argument to pull related tweets:

To do so, we’ll need to add some additional search terms to our list. Run the following code to store your dictionary and queried tweets in your environment:

ngss_dictionary <- c("#NGSSchat OR ngss",
                     '"next generation science standard"',
                     '"next generation science standards"',
                     '"next gen science standard"',
                     '"next gen science standards"')

ngss_tweets_q4 <- search_tweets2(ngss_dictionary,
                              n=5000)

Now let’s create a dictionary for the Common Core State Standards and pass that to our search_tweets() function to get the most recent tweets:

ccss_dictionary <- c("#commoncore", '"common core"')

ccss_tweets <- ccss_dictionary %>% 
  search_tweets2(n=5000, include_rts = FALSE)

Notice that you can use the pipe operator with the search_tweets() function just like you would other functions from the tidyverse.

👉 Your Turn ⤵

  1. In the code chunk below, write a new query based on a STEM area of interest.

  2. Assign your search to a new object called my_tweets or something appropriate.

  3. Output your new dataset using the datatable() function from the DT package and take a quick look.

# your code here

To learn more about constructing search terms using the query argument, enter ?search_tweets in your console and review the documentation for the q= argument.

2b. Other Useful Functions

For your own research, you may be interested in exploring posts by specific users rather than topics, key words, or hashtags. Yes, there is a function for that too!

For example, let’s create another list containing the usernames of the LASER Institute leads using the c() function again and use the get_timelines() function to get the most recent tweets from each of those users:

laser_peeps <- c("sbkellogg", "jrosenberg6432", "yanecnu", "robmoore3", "hollylynnester")

laser_tweets <- laser_peeps %>%
  get_timelines(include_rts=FALSE)

Notice that you can use the pipe operator with the rtweet functions just like you would other functions from the tidyverse.

And let’s use the sample_n() function from the dplyr package to pick 10 random tweets and use select() to select and view just the screenname and text columns that contains the user and the content of their post:

sample_n(laser_tweets, 10) %>%
  select(screen_name, text)

The rtweet package also has handy ts_plot function built into rtweet to take a very quick look at how far back our data set goes:

ts_plot(ngss_tweets_q4, by = "days")

Notice that this effectively creates a {ggplot} time series plot for us. I’ve included the by = argument which by default is set to “days.” It looks like tweets go back 9 days which is the rate limit set by Twitter.

👉 Your Turn ⤵

Use the code chunk below and to change the time series plot above from “days” to “hours” and see what happens.

# your code here

🧶 Knit & Check ✅

Congrats! You made it to the end of the rtweet & the Twitter API supplemental learning lab. Knit your document and check to see if you encounter any errors.

Reach 🧗🏾‍♀

Try one of the following search functions from the rtweet vignette:

  1. get_timelines() Get the most recent 3,200 tweets from users.
  2. stream_tweets() Randomly sample (approximately 1%) from the live stream of all tweets.
  3. get_friends() Retrieve a list of all the accounts a user follows.
  4. get_followers() Retrieve a list of the accounts following a user.
  5. get_favorites() Get the most recently favorited statuses by a user.
  6. get_trends() Discover what’s currently trending in a city.
  7. search_users() Search for 1,000 users with the specific hashtag in their profile bios.

We’ve only scratched the surface of the number of functions available in the rtweets package for searching Twitter. To learn more about the rtweet package, you can find full documentation on CRAN at: https://cran.r-project.org/web/packages/rtweet/rtweet.pdf

Or use the following function to access the package vignette:

vignette("intro", package="rtweet")
Rosenberg, Joshua, Conrad Borchers, Elizabeth B Dyer, Daniel Anderson, and Christian Fischer. 2020. “Understanding Public Sentiment about Educational Reforms: The Next Generation Science Standards on Twitter,” October. http://dx.doi.org/10.31219/osf.io/xymsd.
LS0tCnRpdGxlOiAncnR3ZWV0ICYgdGhlIFR3aXR0ZXIgQVBJJwpzdWJ0aXRsZTogJ1RleHQgTWluaW5nIExlYXJuaW5nIExhYiAxYSAoT3B0aW9uYWwpJwphdXRob3I6ICJZT1VSIE5BTUUgSEVSRSIKZGF0ZTogImByIGZvcm1hdChTeXMuRGF0ZSgpLCclQiAlZSwgJVknKWAiCm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB0cnVlCiAgICB0b2NfZGVwdGg6IDUKICAgIHRvY19mbG9hdDogeWVzCiAgaHRtbF9ub3RlYm9vazoKYmlibGlvZ3JhcGh5OiBsaXQvcmVmZXJlbmNlcy5iaWIKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGVjaG8gPSBUUlVFKQpgYGAKCiMjIDEuIFBSRVBBUkUKClRoZSBUZXh0IE1pbmluZyBMZWFybmluZyBMYWJzIGR1cmluZyB0aGUgc2Vjb25kIHdlZWsgb2YgdGhlIFN1bW1lciBXb3Jrc2hvcCBhcmUgZ3VpZGVkIGJ5IGEgcmVjZW50IHB1YmxpY2F0aW9uIGJ5IEByb3NlbmJlcmcyMDIwIGFuZCB3aWxsIGZvY3VzIG9uIGFuYWx5emluZyB0d2VldHMgYWJvdXQgdGhlIFtOZXh0IEdlbmVyYXRpb24gU2NpZW5jZSBTdGFuZGFyZHNdKGh0dHBzOi8vd3d3Lm5leHRnZW5zY2llbmNlLm9yZykgKE5HU1MpIGFuZCBbQ29tbW9uIENvcmUgU3RhdGUgU3RhbmRhcmRzXShodHRwOi8vd3d3LmNvcmVzdGFuZGFyZHMub3JnKSAoQ0NTUykuIFdlJ2xsIGRpdmUgZGVlcGVyIGludG8gdGhlc2UgdHdlZXRzIGluIFRleHQgTWluaW5nIChUTSkgTW9kdWxlIDE6IFB1YmxpYyBTZW50aW1lbnQgYW5kIHRoZSBTdGF0ZSBTdGFuZGFyZHMuIEZvciBub3csIHRoaXMgc3VwcGxlbWVudGFsIGxlYXJuaW5nIGxhYiBpcyBkZXNpZ25lZCB0byBoZWxwIHlvdSB1bmRlcnN0YW5kIGhvdyBkYXRhIHVzZWQgaW4gdGhlIHN0dWR5IGFuZCB0aGUgbGVhcm5pbmcgbGFicyB3YXMgY29sbGVjdGVkLiBNb3JlIGltcG9ydGFudGx5IHRob3VnaCwgdGhpcyBzdXBwbGVtZW50YWwgbGFiIGlzIHdpbGwgZ2V0IHlvdSB1cCBhbmQgcnVubmluZyB3aXRoIHRoZSBUd2l0dGVyIEFQSSBpbiBjYXNlIHlvdSBhcmUgaW50ZXJlc3RlZCBpbiB1c2luZyBkYXRhIGZyb20gVHdpdHRlciBpbiB5b3VyIG93biByZXNlYXJjaCBvciBldmFsdWF0aW9uIHdvcmsuCgojIyMgMWEuIExvYWQgTGlicmFyaWVzCgojIyMjIHRpZHl2ZXJzZSDwn5OmCgohW10oaW1nL3RpZHl2ZXJzZS5wbmcpe3dpZHRoPSIyMCUifQoKQXMgbm90ZWQgaW4gb3VyIEdldHRpbmcgU3RhcnRlZCBhY3Rpdml0eSwgUiB1c2VzICJwYWNrYWdlcyIgYW5kIGFkZC1vbnMgdGhhdCBlbmhhbmNlIGl0cyBmdW5jdGlvbmFsaXR5LiBPbmUgcGFja2FnZSB0aGF0IHdlJ2xsIGJlIHVzaW5nIGV4dGVuc2l2ZWx5IGlzIHt0aWR5dmVyc2V9LiBUaGUge3RpZHl2ZXJzZX0gcGFja2FnZSBpcyBhY3R1YWxseSBhIFtjb2xsZWN0aW9uIG9mIFIgcGFja2FnZXNdKGh0dHBzOi8vd3d3LnRpZHl2ZXJzZS5vcmcvcGFja2FnZXMpIGRlc2lnbmVkIGZvciByZWFkaW5nLCB3cmFuZ2xpbmcsIGFuZCBleHBsb3JpbmcgZGF0YSBhbmQgd2hpY2ggYWxsIHNoYXJlIGFuIHVuZGVybHlpbmcgZGVzaWduIHBoaWxvc29waHksIGdyYW1tYXIsIGFuZCBkYXRhIHN0cnVjdHVyZXMuCgpDbGljayB0aGUgZ3JlZW4gYXJyb3cgaW4gdGhlIHJpZ2h0IGNvcm5lciBvZiB0aGUgImNvZGUgY2h1bmsiIHRoYXQgZm9sbG93cyB0byBsb2FkIHRoZSB7dGlkeXZlcnNlfSBsaWJyYXJ5LgoKYGBge3J9CmxpYnJhcnkodGlkeXZlcnNlKQpgYGAKCkFnYWluLCBkb24ndCB3b3JyeSBpZiB5b3Ugc2F3IGEgbnVtYmVyIG9mIG1lc3NhZ2VzOiB0aG9zZSBwcm9iYWJseSBtZWFuIHRoYXQgdGhlIHRpZHl2ZXJzZSBsb2FkZWQganVzdCBmaW5lLiBBbnkgY29uZmxpY3RzIHlvdSBtYXkgaGF2ZSBzZWVuIG1lYW4gdGhhdCBmdW5jdGlvbnMgaW4gdGhlc2UgcGFja2FnZXMgeW91IGxvYWRlZCBoYXZlIHRoZSBzYW1lIG5hbWUgYXMgZnVuY3Rpb25zIGluIG90aGVyIHBhY2thZ2VzIGFuZCBSIHdpbGwgZGVmYXVsdCB0byBmdW5jdGlvbiBmcm9tIHRoZSBsYXN0IGxvYWRlZCBwYWNrYWdlIHVubGVzcyB5b3Ugc3BlY2lmeSBvdGhlcndpc2UuCgojIyMjIHJ0d2VldCDwn5OmCgohW10oaW1nL3J0d2VldC5qcGVnKXt3aWR0aD0iMjElIn0KClRoZSBgcnR3ZWV0YCBwYWNrYWdlIHByb3ZpZGVzIHVzZXJzIGEgcmFuZ2Ugb2YgZnVuY3Rpb25zIGRlc2lnbmVkIHRvIGV4dHJhY3QgZGF0YSBmcm9tIFR3aXR0ZXIncyBSRVNUIGFuZCBzdHJlYW1pbmcgQVBJcyBhbmQgaGFzIHRocmVlIG1haW4gZ29hbHM6CgoxLiAgRm9ybXVsYXRlIGFuZCBzZW5kIHJlcXVlc3RzIHRvIFR3aXR0ZXIncyBSRVNUIGFuZCBzdHJlYW0gQVBJcy4KCjIuICBSZXRyaWV2ZSBhbmQgaXRlcmF0ZSBvdmVyIHJldHVybmVkIGRhdGEuCgozLiAgV3JhbmdsaW5nIGRhdGEgaW50byB0aWR5IHN0cnVjdHVyZXMuCgpMZXQncyBsb2FkIHRoZSB7cnR3ZWV0fSBwYWNrYWdlIHRoYXQgd2UnbGwgYmUgdXNpbmcgdG8gYWNjb21wbGlzaCBhbGwgdGhyZWUgb2YgdGhlIGdvYWxzIGxpc3RlZCBhYm92ZToKCmBgYHtyfQpsaWJyYXJ5KHJ0d2VldCkKYGBgCgojIyMgMWIuIEF1dGhvcml6ZSBSU3R1ZGlvCgpJbiBvcmRlciB0byBhdXRob3JpemUgUiB0byB1c2UgeW91ciBUd2l0dGVyIEFwcCB0byByZXRyaWV2ZSBkYXRhLCB5b3UnbGwgbmVlZCB0byBjcmVhdGUgYSBwZXJzb25hbCBUd2l0dGVyIHRva2VuIGJ5IGNvbXBsZXRpbmcgdGhlIGZvbGxvd2luZyBzdGVwczoKCjEuICBOYXZpZ2F0ZSB0byBbZGV2ZWxvcGVyLnR3aXR0ZXIuY29tL2VuL2FwcHNdKGh0dHBzOi8vZGV2ZWxvcGVyLnR3aXR0ZXIuY29tL2VuL2FwcHMpIGFuZCBzZWxlY3QgeW91ciBUd2l0dGVyIGFwcAoyLiAgQ2xpY2sgdGhlIHRhYiBsYWJlbGVkIGBLZXlzIGFuZCB0b2tlbnNgIHRvIHJldHJpZXZlIHlvdXIga2V5cy4KMy4gIExvY2F0ZSB0aGUgYENvbnN1bWVyIEFQSSBrZXlzYCAoYWthICJBUEkgU2VjcmV0IikuCgohW10oaW1nL2NyZWF0ZS1hcHAtNi5wbmcpe3dpZHRoPSI4MCUifQoKMy4gIFNjcm9sbCBkb3duIHRvIGBBY2Nlc3MgdG9rZW4gJiBhY2Nlc3MgdG9rZW4gc2VjcmV0YCBhbmQgY2xpY2sgYENyZWF0ZWAKCiFbXShpbWcvY3JlYXRlLWFwcC03LnBuZyl7d2lkdGg9IjgwJSJ9Cgo0LiAgQ29weSBhbmQgcGFzdGUgdGhlIGZvbGxvd2luZyBjb2RlIGludG8gdGhlIFIgU2NyaXB0IGZpbGUgbmFtZWQgYHR3aXR0ZXItYXV0aC5SYCBsb2NhdGVkIGluIGZpbGVzIHBhbmUsIHJlcGxhY2UgdGhlIGZvdXIgZmFrZSBrZXlzIHdpdGggeW91ciBvd24sIGFuZCBwYXNzIHRoZW0gYWxvbmcgdG8gYGNyZWF0ZV90b2tlbigpYCBmdW5jdGlvbi4KCmBgYHtyIGFwaS1rZXlzLCBldmFsPUZBTFNFfQojIyBzdG9yZSBhcGkga2V5cyAodGhlc2UgYXJlIGZha2UgZXhhbXBsZSB2YWx1ZXM7IHJlcGxhY2Ugd2l0aCB5b3VyIG93biBrZXlzKQphcHBfbmFtZSA8LSAiVGV4dCBNaW5pbmcgaW4gRWR1Y2F0aW9uIgphcGlfa2V5IDwtICJhZllTNHZiSWxQQWowOTZFNjBjNFcxZmlLIgphcGlfc2VjcmV0X2tleSA8LSAiYkk5MWtxbnFGb05DclpGYnNqQVdIRDRnSjkxTFFBaGRDSlhDajN5c2NmdVVMdE5rdXUiCmFjY2Vzc190b2tlbiA8LSAiOTU1MTQ1MTI2Mi13SzJFbUE5NDJreFpZSXdhNUxNS1pvUUE0WGMydXlJaUV3dTJZWEwiCmFjY2Vzc190b2tlbl9zZWNyZXQgPC0gIjl2cGlTR0tnMWZJUFF0eGM1ZDVFU2lGbFpRcGZia25FTjFmMW0yeGU1Ynl3NyIKCiMjIGF1dGhlbnRpY2F0ZSB2aWEgd2ViIGJyb3dzZXIKdG9rZW4gPC0gY3JlYXRlX3Rva2VuKAogIGFwcCA9IGFwcF9uYW1lLAogIGNvbnN1bWVyX2tleSA9IGFwaV9rZXksCiAgY29uc3VtZXJfc2VjcmV0ID0gYXBpX3NlY3JldF9rZXksCiAgYWNjZXNzX3Rva2VuID0gYWNjZXNzX3Rva2VuLAogIGFjY2Vzc19zZWNyZXQgPSBhY2Nlc3NfdG9rZW5fc2VjcmV0KQpgYGAKCioqTm90ZSoqOiB0aGVzZSBrZXlzIGFyZSBuYW1lZCBzZWNyZXQgZm9yIGEgcmVhc29uLiBXZSBzdG9yZSB0aGVzZSB1cCBpbiBhIHNlcGFyYXRlIFIgc2NyaXB0IGZpbGUgcmF0aGVyIHRoYW4gaW4gYSBSIE1hcmtkb3duIGZpbGUgdGhhdCB5b3Ugd2lsbCBldmVudHVhbGx5IHNoYXJlLgoKIyMjIyBDaGVjayBBdXRob3JpemF0aW9uCgpUaGUgYGNyZWF0ZV90b2tlbigpYCBmdW5jdGlvbiBzaG91bGQgYXV0b21hdGljYWxseSBzYXZlIHlvdXIgdG9rZW4gYXMgYW4gZW52aXJvbm1lbnQgdmFyaWFibGUuIFNvIG5leHQgdGltZSB5b3Ugc3RhcnQgYW4gUiBzZXNzaW9uIC0tIG9uIHRoZSBzYW1lIG1hY2hpbmUgLS0gcnR3ZWV0IHNob3VsZCDwn6SeIGF1dG9tYXRpY2FsbHkgZmluZCB5b3VyIHRva2VuLgoKMTAuIFRvIG1ha2Ugc3VyZSBpdCB3b3JrcywgeW91IGNhbiBydW4gdGhlIGZvbGxvd2luZyBjb2RlIGFuZCBjaGVjayB0byBtYWtlIHN1cmUgdGhlIGFwcCBuYW1lIGFuZCBgYXBpX2tleWAgbWF0Y2ggdGhvc2Ugb24geW91ciBkZXYgYWNjb3VudC4KCmBgYHtyIGdldC10b2tlbn0KIyMgY2hlY2sgdG8gc2VlIGlmIHRoZSB0b2tlbiBpcyBsb2FkZWQKZ2V0X3Rva2VuKCkKYGBgCgpUaGF0J3MgaXQhIFlvdSdyZSByZWFkeSByZWFkeSB0byBzdGFydCB3cmFuZ2xpbmcgc29tZSB0d2VldHMhISEKCiMjIDIuIFdSQU5HTEUKCiMjIyAyYS4gU2VhcmNoIFR3ZWV0cwoKIyMjIyBDb25zdHJ1Y3RpbmcgYSBRdWVyeQoKU2luY2Ugb25lIG9mIG91ciBnb2FscyBmb3IgVE0gTW9kdWxlIDEgaXMgYSBzaW1wbGlzdGljIHJlcGxpY2F0aW9uIG9mIHRoZSBzdHVkeSBieSBbQHJvc2VuYmVyZzIwMjBdLCBsZXQncyBiZWdpbiBieSBpbnRyb2R1Y2luZyB0aGUgYHNlYXJjaF90d2VldHMoKWAgZnVuY3Rpb24gdG8gbWluZSBzb21lIHR3ZWV0cyBhYm91dCB0aGUgTmV4dCBHZW5lcmF0aW9uIFNjaWVuY2UgU3RhbmRhcmRzLgoKVXNlIHRoZSBjb2RlIGNodW5rIGJlbG93IHRvIHJ1biB0aGUgZm9sbG93aW5nIGNvZGUgdG8gcmVxdWVzdCBmcm9tIFR3aXR0ZXIncyBBUEkgNSwwMDAgdHdlZXRzIGNvbnRhaW5pbmcgdGhlIE5HU1NjaGF0IGhhc2h0YWcgYW5kIHN0b3JlIGFzIGEgbmV3IGRhdGEgZnJhbWUgY2FsbGVkIGBuZ3NzX3R3ZWV0c19xMWA6CgpgYGB7cn0Kbmdzc190d2VldHNfcTEgPC0gc2VhcmNoX3R3ZWV0cyhxID0gIiNOR1NTY2hhdCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG4gPSA1MDAwKQpgYGAKCk5vdGUgdGhhdCB0aGUgZmlyc3QgYXJndW1lbnQgdGhlIGBzZWFyY2hfdHdlZXRzKClgIGZ1bmN0aW9uIGV4cGVjdHMsIGBxID1gLCBpcyB0aGUgc2VhcmNoIHRlcm0gaW5jbHVkZWQgaW4gcXVvdGF0aW9uIG1hcmtzIGFuZCB0aGF0IGBuID1gIHNwZWNpZmllcyB0aGUgbWF4aW11bSBudW1iZXIgb2YgdHdlZXRzIHdlIHdhbnQuCgojIyMjIPCfkYkgWW91ciBUdXJuIOKktQoKVXNlIHRoZSBjb2RlIGNodW5rIGJlbG93IHRvIHZpZXcgeW91ciBuZXcgYG5nc3NfdHdlZXRzX3ExYCBkYXRhIGZyYW1lIHVzaW5nIG9uZSBvZiB0aGUgbWV0aG9kcyBwcmV2aW91c2x5IGludHJvZHVjZWQgdG8gaGVscCBhbnN3ZXIgdGhlIGZvbGxvd2luZyBxdWVzdGlvbnM6CgpgYGB7cn0KIyB5b3VyIGNvZGUgaGVyZQoKYGBgCgoxLiAgSG93IG1hbnkgdHdlZXRzIGRpZCBvdXIgZmlyc3QgcXVlcnkgdXNpbmcgdGhlIFR3aXR0ZXIgQVBJIGFjdHVhbGx5IHJldHVybj8gSG93IG1hbnkgdmFyaWFibGVzPwoKICAgIC0gICAKCjIuICBXaHkgZG8geW91IHRoaW5rIG91ciBxdWVyeSBwdWxsZWQgaW4gZmFyIGxlc3MgdGhhbiA1LDAwMCB0d2VldHMgcmVxdWVzdGVkPyBbSGludF0oaHR0cHM6Ly9jYmFpbC5naXRodWIuaW8vdGV4dGFzZGF0YS9hcGlzL3JtYXJrZG93bi9BcHBsaWNhdGlvbl9Qcm9ncmFtbWluZ19pbnRlcmZhY2VzLmh0bWwjcmF0ZS1saW1pdGluZykuCgogICAgLSAgIAoKMy4gIEhvdyBtYW55IHR3ZWV0cyBhcmUgcmV0dXJuZWQgaWYgeW91IGRvbid0IGluY2x1ZGUgdGhlIGBuID1gIGFyZ3VtZW50PwoKICAgIC0gICAKCjQuICBEb2VzIG91ciBxdWVyeSBhbHNvIGluY2x1ZGUgcmV0d2VldHM/IEhvdyBkbyB5b3Uga25vdz8KCiAgICAtICAgCgo1LiAgRG9lcyBjYXBpdGFsaXphdGlvbiBpbiB5b3VyIHF1ZXJ5IG1hdHRlcj8gVXNlIHRoZSBjb2RlIGNodW5rIGJlbG93IHRvIGZpbmQgb3V0LgoKYGBge3J9CiMgeW91ciBjb2RlIGhlcmUgCgpgYGAKCiMjIyMgVXNpbmcgdGhlIE9SIE9wZXJhdG9yCgpJbiAqVW5kZXJzdGFuZGluZyBwdWJsaWMgc2VudGltZW50IGFib3V0IGVkdWNhdGlvbmFsIHJlZm9ybXM6IFRoZSBOZXh0IEdlbmVyYXRpb24gU2NpZW5jZSBTdGFuZGFyZHMgb24gVHdpdHRlciosIEByb3NlbmJlcmcyMDIwIGFjY2Vzc2VkIHR3ZWV0cyBhbmQgdXNlciBpbmZvcm1hdGlvbiBmcm9tIHRoZSBoYXNodGFnLWJhc2VkIFwjTkdTU2NoYXQgb25saW5lIGNvbW11bml0eSwgaW5jbHVkaW5nIGFsbCB0d2VldHMgdGhhdCB1c2VkIHRoZSBmb2xsb3dpbmcgcGhyYXNlczogIm5nc3MiLCAibmV4dCBnZW5lcmF0aW9uIHNjaWVuY2Ugc3RhbmRhcmQvcyIsICJuZXh0IGdlbiBzY2llbmNlIHN0YW5kYXJkL3MiLiBOb3RlIHRoYXQgIi8iIGluZGljYXRlcyBhbiBhZGRpdGlvbmFsIHBocmFzZSBmZWF0dXJpbmcgdGhlIHJlc3BlY3RpdmUgcGx1cmFsIGZvcm0uCgpMZXQncyBtb2RpZnkgb3VyIHF1ZXJ5IHVzaW5nIHRoZSBgT1JgIG9wZXJhdG9yIHRvIGFsc28gaW5jbHVkZSAibmdzcyIgc28gaXQgd2lsbCByZXR1cm4gdHdlZXRzIGNvbnRhaW5pbmcgZWl0aGVyIFwjTkdTU2NoYXQgb3IgIm5nc3MiIGFuZCBhc3NpZ24gdG8gYG5nc3Nfb3JfdHdlZXRzYDoKCmBgYHtyfQpuZ3NzX3R3ZWV0c19xMiA8LSBzZWFyY2hfdHdlZXRzKHEgPSAiI05HU1NjaGF0IE9SIG5nc3MiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuID0gNTAwMCkKCm5nc3NfdHdlZXRzX3EyCmBgYAoKIyMjIyDwn5GJIFlvdXIgVHVybiDipLUKCkluIHRoZSBmb2xsb3dpbmcgY29kZSBjaHVuaywgdHJ5IGluY2x1ZGluZyBib3RoIHNlYXJjaCB0ZXJtcyBidXQgZXhjbHVkaW5nIHRoZSBgT1JgIG9wZXJhdG9yIHRvIGFuc3dlciB0aGUgcXVlc3Rpb25zIGJlbG93OgoKYGBge3J9CiMgeW91ciBjb2RlIGhlcmUKCmBgYAoKMS4gIERvZXMgZXhjbHVkaW5nIHRoZSBgT1JgIG9wZXJhdG9yIHJldHVybiBtb3JlIHR3ZWV0cywgdGhlIHNhbWUgbnVtYmVyIG9mIHR3ZWV0cywgb3IgZmV3ZXIgdHdlZXRzPyBXaHk/CgogICAgLSAgIAoKMi4gIERvZXMgb3VyIHF1ZXJ5IGFsc28gaW5jbHVkZSB0d2VldHMgY29udGFpbmluZyB0aGUgXCNuZ3NzIGhhc2h0YWc/CgogICAgLSAgIAoKMy4gIFdoYXQgb3RoZXIgdXNlZnVsIGFyZ3VtZW50cyBkb2VzIHRoZSBgc2VhcmNoX3R3ZWV0KClgIGZ1bmN0aW9uIGNvbnRhaW4/IFRyeSBhZGRpbmcgb25lIGFuZCBzZWUgd2hhdCBoYXBwZW5zLgoKICAgIC0gICAKCioqSGludCoqOiBVc2UgdGhlIGA/c2VhcmNoX3R3ZWV0c2AgaGVscCBmdW5jdGlvbiB0byBsZWFybiBtb3JlIGFib3V0IHRoZSBgcWAgYXJndW1lbnQgYW5kIG90aGVyIGFyZ3VtZW50cyBmb3IgY29tcG9zaW5nIHNlYXJjaCBxdWVyaWVzLgoKIyMjIyBVc2luZyBNdWx0aXBsZSBRdWVyaWVzCgpVbmZvcnR1bmF0ZWx5LCB0aGUgYE9SYCBvcGVyYXRvciB3aWxsIG9ubHkgZ2V0IHVzIHNvIGZhci4gSW4gb3JkZXIgdG8gaW5jbHVkZSB0aGUgYWRkaXRpb25hbCBzZWFyY2ggdGVybXMsIHdlIHdpbGwgbmVlZCB0byB1c2UgdGhlIGBjKClgIGZ1bmN0aW9uIHRvIGNvbWJpbmUgb3VyIHNlYXJjaCB0ZXJtcyBpbnRvIGEgc2luZ2xlIGxpc3QuCgpUaGUgYHJ0d2VldHNgIHBhY2thZ2UgaGFzIGFuIGFkZGl0aW9uYWwgYHNlYXJjaF90d2VldHMyKClgIGZ1bmN0aW9uIGZvciB1c2luZyBtdWx0aXBsZSBxdWVyaWVzIGluIGEgc2VhcmNoLiBUbyBkbyB0aGlzLCBlaXRoZXIgd3JhcCBzaW5nbGUgcXVvdGVzIGFyb3VuZCBhIHNlYXJjaCBxdWVyeSB1c2luZyBkb3VibGUgcXVvdGVzLCBlLmcuLCBgcSA9ICcibmV4dCBnZW4gc2NpZW5jZSBzdGFuZGFyZCInYCBvciBlc2NhcGUgZWFjaCBpbnRlcm5hbCBkb3VibGUgcXVvdGUgd2l0aCBhIHNpbmdsZSBiYWNrc2xhc2gsIGUuZy4sIGBxID0gIlwibmV4dCBnZW4gc2NpZW5jZSBzdGFuZGFyZFwiImAuCgpDb3B5IGFuZCBwYXN0IHRoZSBmb2xsb3dpbmcgY29kZSB0byBzdG9yZSB0aGUgcmVzdWx0cyBvZiBvdXIgcXVlcnkgaW4gYG5nc3NfdHdlZXRzYDoKCmBgYHtyfQpuZ3NzX3R3ZWV0c19xMyA8LSBzZWFyY2hfdHdlZXRzMihxID0gYygiI05HU1NjaGF0IE9SIG5nc3MiLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAnIm5leHQgZ2VuZXJhdGlvbiBzY2llbmNlIHN0YW5kYXJkIicpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBuID0gNTAwMCkKYGBgCgpOb3RpY2UgdGhlIHVuaXF1ZSBzeW50YXggcmVxdWlyZWQgZm9yIHRoZSBxdWVyeSBhcmd1bWVudC4gRm9yIGV4YW1wbGUsIHdoZW4gIk9SIiBpcyBlbnRlcmVkIGJldHdlZW4gc2VhcmNoIHRlcm1zLMKgYHF1ZXJ5ID0gIiNOR1NTY2hhdCBPUiBuZ3NzImAsIFR3aXR0ZXIncyBSRVNUIEFQSSBzaG91bGQgcmV0dXJuIGFueSB0d2VldCB0aGF0IGNvbnRhaW5zIGVpdGhlciAiXCNOR1NTY2hhdCIgb3IgIm5nc3MuIiBJdCBpcyBhbHNvIHBvc3NpYmxlIHRvIHNlYXJjaCBmb3IgZXhhY3QgcGhyYXNlcyB1c2luZyBkb3VibGUgcXVvdGVzLiBUbyBkbyB0aGlzLCBlaXRoZXIgd3JhcCBzaW5nbGUgcXVvdGVzIGFyb3VuZCBhIHNlYXJjaCBxdWVyeSB1c2luZyBkb3VibGUgcXVvdGVzLCBlLmcuLMKgYHEgPSAnIm5leHQgZ2VuZXJhdGlvbiBzY2llbmNlIHN0YW5kYXJkIidgIGFzIHdlIGRpZCBhYm92ZSwgb3IgZXNjYXBlIGVhY2ggaW50ZXJuYWwgZG91YmxlIHF1b3RlIHdpdGggYSBzaW5nbGUgYmFja3NsYXNoLCBlLmcuLMKgYHEgPSAiXCJuZXh0IGdlbmVyYXRpb24gc2NpZW5jZSBzdGFuZGFyZFwiImAuCgojIyMjIE91ciBGaXJzdCBEaWN0aW9uYXJ5CgpXZSBzdGlsbCBoYXZlIGEgZmV3IHF1ZXJpZXMgdG8gYWRkIGluIG9yZGVyIHRvIHJlcGxpY2F0ZSB0aGUgYXBwcm9hY2ggYnkgUm9zZW5iZXJnIGV0IGFsLCBidXQgZGVhbGluZyB3aXRoIHRoYXQgbWFueSBxdWVyaWVzIGluc2lkZSBhIHNpbmdsZSBmdW5jdGlvbiBpcyBhIGJpdCB0ZWRpb3VzLgoKTGV0J3MgZ28gYWhlYWQgYW5kIGNyZWF0ZSBvdXIgdmVyeSBmaXJzdCAiZGljdGlvbmFyeSIgLS0tIHdlJ2xsIGxlYXJuIG1vcmUgYWJvdXQgZGljdGlvbmFyeS1iYXNlZCBhcHByb2FjaGVzIHRvIHRleHQgbWluaW5nIGluIExlYXJuaW5nIExhYiAzIC0tLSBmb3IgaWRlbnRpZnlpbmcgdHdlZXRzIHJlbGF0ZWQgdG8gdGhlIE5HU1Mgc3RhbmRhcmRzLCBhbmQgdGhlbiBwYXNzIHRoYXQgZGljdGlvbmFyeSB0byB0aGUgYHEgPWAgcXVlcnkgYXJndW1lbnQgdG8gcHVsbCByZWxhdGVkIHR3ZWV0czoKClRvIGRvIHNvLCB3ZSdsbCBuZWVkIHRvIGFkZCBzb21lIGFkZGl0aW9uYWwgc2VhcmNoIHRlcm1zIHRvIG91ciBsaXN0LiBSdW4gdGhlIGZvbGxvd2luZyBjb2RlIHRvIHN0b3JlIHlvdXIgZGljdGlvbmFyeSBhbmQgcXVlcmllZCB0d2VldHMgaW4geW91ciBlbnZpcm9ubWVudDoKCmBgYHtyfQpuZ3NzX2RpY3Rpb25hcnkgPC0gYygiI05HU1NjaGF0IE9SIG5nc3MiLAogICAgICAgICAgICAgICAgICAgICAnIm5leHQgZ2VuZXJhdGlvbiBzY2llbmNlIHN0YW5kYXJkIicsCiAgICAgICAgICAgICAgICAgICAgICcibmV4dCBnZW5lcmF0aW9uIHNjaWVuY2Ugc3RhbmRhcmRzIicsCiAgICAgICAgICAgICAgICAgICAgICcibmV4dCBnZW4gc2NpZW5jZSBzdGFuZGFyZCInLAogICAgICAgICAgICAgICAgICAgICAnIm5leHQgZ2VuIHNjaWVuY2Ugc3RhbmRhcmRzIicpCgpuZ3NzX3R3ZWV0c19xNCA8LSBzZWFyY2hfdHdlZXRzMihuZ3NzX2RpY3Rpb25hcnksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG49NTAwMCkKYGBgCgpOb3cgbGV0J3MgY3JlYXRlIGEgZGljdGlvbmFyeSBmb3IgdGhlIENvbW1vbiBDb3JlIFN0YXRlIFN0YW5kYXJkcyBhbmQgcGFzcyB0aGF0IHRvIG91csKgYHNlYXJjaF90d2VldHMoKWDCoGZ1bmN0aW9uIHRvIGdldCB0aGUgbW9zdCByZWNlbnQgdHdlZXRzOgoKYGBge3J9CmNjc3NfZGljdGlvbmFyeSA8LSBjKCIjY29tbW9uY29yZSIsICciY29tbW9uIGNvcmUiJykKCmNjc3NfdHdlZXRzIDwtIGNjc3NfZGljdGlvbmFyeSAlPiUgCiAgc2VhcmNoX3R3ZWV0czIobj01MDAwLCBpbmNsdWRlX3J0cyA9IEZBTFNFKQpgYGAKCk5vdGljZSB0aGF0IHlvdSBjYW4gdXNlIHRoZSBwaXBlIG9wZXJhdG9yIHdpdGggdGhlwqBgc2VhcmNoX3R3ZWV0cygpYMKgZnVuY3Rpb24ganVzdCBsaWtlIHlvdSB3b3VsZCBvdGhlciBmdW5jdGlvbnMgZnJvbSB0aGUgdGlkeXZlcnNlLgoKIyMjIyDwn5GJIFlvdXIgVHVybiDipLUKCjEuICBJbiB0aGUgY29kZSBjaHVuayBiZWxvdywgd3JpdGUgYSBuZXcgcXVlcnkgYmFzZWQgb24gYSBTVEVNIGFyZWEgb2YgaW50ZXJlc3QuCgoyLiAgQXNzaWduIHlvdXIgc2VhcmNoIHRvIGEgbmV3IG9iamVjdCBjYWxsZWQgYG15X3R3ZWV0c2Agb3Igc29tZXRoaW5nIGFwcHJvcHJpYXRlLgoKMy4gIE91dHB1dCB5b3VyIG5ldyBkYXRhc2V0IHVzaW5nIHRoZSBgZGF0YXRhYmxlKClgIGZ1bmN0aW9uIGZyb20gdGhlIGBEVGAgcGFja2FnZSBhbmQgdGFrZSBhIHF1aWNrIGxvb2suCgpgYGB7cn0KIyB5b3VyIGNvZGUgaGVyZQoKYGBgCgpUbyBsZWFybiBtb3JlIGFib3V0IGNvbnN0cnVjdGluZyBzZWFyY2ggdGVybXMgdXNpbmcgdGhlIHF1ZXJ5IGFyZ3VtZW50LCBlbnRlciBgP3NlYXJjaF90d2VldHNgIGluIHlvdXIgY29uc29sZSBhbmQgcmV2aWV3IHRoZSBkb2N1bWVudGF0aW9uIGZvciB0aGUgYHE9YCBhcmd1bWVudC4KCiMjIyAyYi4gT3RoZXIgVXNlZnVsIEZ1bmN0aW9ucwoKRm9yIHlvdXIgb3duIHJlc2VhcmNoLCB5b3UgbWF5IGJlIGludGVyZXN0ZWQgaW4gZXhwbG9yaW5nIHBvc3RzIGJ5IHNwZWNpZmljIHVzZXJzIHJhdGhlciB0aGFuIHRvcGljcywga2V5IHdvcmRzLCBvciBoYXNodGFncy4gWWVzLCB0aGVyZSBpcyBhIGZ1bmN0aW9uIGZvciB0aGF0IHRvbyEKCkZvciBleGFtcGxlLCBsZXQncyBjcmVhdGUgYW5vdGhlciBsaXN0IGNvbnRhaW5pbmcgdGhlIHVzZXJuYW1lcyBvZiB0aGUgTEFTRVIgSW5zdGl0dXRlIGxlYWRzIHVzaW5nIHRoZSBgYygpYCBmdW5jdGlvbiBhZ2FpbiBhbmQgdXNlIHRoZSBgZ2V0X3RpbWVsaW5lcygpYCBmdW5jdGlvbiB0byBnZXQgdGhlIG1vc3QgcmVjZW50IHR3ZWV0cyBmcm9tIGVhY2ggb2YgdGhvc2UgdXNlcnM6CgpgYGB7ciwgZXZhbD1UUlVFfQpsYXNlcl9wZWVwcyA8LSBjKCJzYmtlbGxvZ2ciLCAianJvc2VuYmVyZzY0MzIiLCAieWFuZWNudSIsICJyb2Jtb29yZTMiLCAiaG9sbHlseW5uZXN0ZXIiKQoKbGFzZXJfdHdlZXRzIDwtIGxhc2VyX3BlZXBzICU+JQogIGdldF90aW1lbGluZXMoaW5jbHVkZV9ydHM9RkFMU0UpCmBgYAoKTm90aWNlIHRoYXQgeW91IGNhbiB1c2UgdGhlIHBpcGUgb3BlcmF0b3Igd2l0aCB0aGUgYHJ0d2VldGAgZnVuY3Rpb25zIGp1c3QgbGlrZSB5b3Ugd291bGQgb3RoZXIgZnVuY3Rpb25zIGZyb20gdGhlIHRpZHl2ZXJzZS4KCkFuZCBsZXQncyB1c2UgdGhlIGBzYW1wbGVfbigpYCBmdW5jdGlvbiBmcm9tIHRoZSBgZHBseXJgIHBhY2thZ2UgdG8gcGljayAxMCByYW5kb20gdHdlZXRzIGFuZCB1c2UgYHNlbGVjdCgpYCB0byBzZWxlY3QgYW5kIHZpZXcganVzdCB0aGUgYHNjcmVlbm5hbWVgIGFuZCBgdGV4dGAgY29sdW1ucyB0aGF0IGNvbnRhaW5zIHRoZSB1c2VyIGFuZCB0aGUgY29udGVudCBvZiB0aGVpciBwb3N0OgoKYGBge3IsIGV2YWw9VFJVRX0Kc2FtcGxlX24obGFzZXJfdHdlZXRzLCAxMCkgJT4lCiAgc2VsZWN0KHNjcmVlbl9uYW1lLCB0ZXh0KQpgYGAKClRoZSBgcnR3ZWV0YCBwYWNrYWdlIGFsc28gaGFzIGhhbmR5IGB0c19wbG90YCBmdW5jdGlvbiBidWlsdCBpbnRvIGBydHdlZXRgIHRvIHRha2UgYSB2ZXJ5IHF1aWNrIGxvb2sgYXQgaG93IGZhciBiYWNrIG91ciBkYXRhIHNldCBnb2VzOgoKYGBge3IsIGV2YWw9VFJVRX0KdHNfcGxvdChuZ3NzX3R3ZWV0c19xNCwgYnkgPSAiZGF5cyIpCmBgYAoKTm90aWNlIHRoYXQgdGhpcyBlZmZlY3RpdmVseSBjcmVhdGVzIGEge2dncGxvdH0gdGltZSBzZXJpZXMgcGxvdCBmb3IgdXMuIEkndmUgaW5jbHVkZWQgdGhlIGBieSA9YCBhcmd1bWVudCB3aGljaCBieSBkZWZhdWx0IGlzIHNldCB0byAiZGF5cyIuIEl0IGxvb2tzIGxpa2UgdHdlZXRzIGdvIGJhY2sgOSBkYXlzIHdoaWNoIGlzIHRoZSByYXRlIGxpbWl0IHNldCBieSBUd2l0dGVyLgoKIyMjIyDwn5GJIFlvdXIgVHVybiDipLUKClVzZSB0aGUgY29kZSBjaHVuayBiZWxvdyBhbmQgdG8gY2hhbmdlIHRoZSB0aW1lIHNlcmllcyBwbG90IGFib3ZlIGZyb20gImRheXMiIHRvICJob3VycyIgYW5kIHNlZSB3aGF0IGhhcHBlbnMuCgpgYGB7cn0KIyB5b3VyIGNvZGUgaGVyZQoKYGBgCgojIyMg8J+ntsKgS25pdCAmIENoZWNrwqDinIUKCkNvbmdyYXRzISBZb3UgbWFkZSBpdCB0byB0aGUgZW5kIG9mIHRoZSAqKnJ0d2VldCAmIHRoZSBUd2l0dGVyIEFQSSoqIHN1cHBsZW1lbnRhbCBsZWFybmluZyBsYWIuIEtuaXQgeW91ciBkb2N1bWVudCBhbmQgY2hlY2sgdG8gc2VlIGlmIHlvdSBlbmNvdW50ZXIgYW55IGVycm9ycy4KCiMjIyBSZWFjaCDwn6eX8J+PvuKAjeKZgAoKVHJ5IG9uZSBvZiB0aGUgZm9sbG93aW5nIHNlYXJjaCBmdW5jdGlvbnMgZnJvbSB0aGUgYHJ0d2VldGAgdmlnbmV0dGU6CgoxLiAgYGdldF90aW1lbGluZXMoKWAgR2V0IHRoZSBtb3N0IHJlY2VudCAzLDIwMCB0d2VldHMgZnJvbSB1c2Vycy4KMi4gIGBzdHJlYW1fdHdlZXRzKClgIFJhbmRvbWx5IHNhbXBsZSAoYXBwcm94aW1hdGVseSAxJSkgZnJvbSB0aGUgbGl2ZSBzdHJlYW0gb2YgYWxsIHR3ZWV0cy4KMy4gIGBnZXRfZnJpZW5kcygpYCBSZXRyaWV2ZSBhIGxpc3Qgb2YgYWxsIHRoZSBhY2NvdW50cyBhIHVzZXIgZm9sbG93cy4KNC4gIGBnZXRfZm9sbG93ZXJzKClgIFJldHJpZXZlIGEgbGlzdCBvZiB0aGUgYWNjb3VudHMgZm9sbG93aW5nIGEgdXNlci4KNS4gIGBnZXRfZmF2b3JpdGVzKClgIEdldCB0aGUgbW9zdCByZWNlbnRseSBmYXZvcml0ZWQgc3RhdHVzZXMgYnkgYSB1c2VyLgo2LiAgYGdldF90cmVuZHMoKWAgRGlzY292ZXIgd2hhdCdzIGN1cnJlbnRseSB0cmVuZGluZyBpbiBhIGNpdHkuCjcuICBgc2VhcmNoX3VzZXJzKClgIFNlYXJjaCBmb3IgMSwwMDAgdXNlcnMgd2l0aCB0aGUgc3BlY2lmaWMgaGFzaHRhZyBpbiB0aGVpciBwcm9maWxlIGJpb3MuCgpXZSd2ZSBvbmx5IHNjcmF0Y2hlZCB0aGUgc3VyZmFjZSBvZiB0aGUgbnVtYmVyIG9mIGZ1bmN0aW9ucyBhdmFpbGFibGUgaW4gdGhlIGBydHdlZXRzYCBwYWNrYWdlIGZvciBzZWFyY2hpbmcgVHdpdHRlci4gVG8gbGVhcm4gbW9yZSBhYm91dCB0aGUgYHJ0d2VldGAgcGFja2FnZSwgeW91IGNhbiBmaW5kIGZ1bGwgZG9jdW1lbnRhdGlvbiBvbiBDUkFOIGF0OiA8aHR0cHM6Ly9jcmFuLnItcHJvamVjdC5vcmcvd2ViL3BhY2thZ2VzL3J0d2VldC9ydHdlZXQucGRmPgoKT3IgdXNlIHRoZSBmb2xsb3dpbmcgZnVuY3Rpb24gdG8gYWNjZXNzIHRoZSBwYWNrYWdlIHZpZ25ldHRlOgoKYGBge3IgcnR3ZWV0LXZpZ25ldHRlLCBldmFsPUZ9CnZpZ25ldHRlKCJpbnRybyIsIHBhY2thZ2U9InJ0d2VldCIpCmBgYAo=